/*
  FUSE: Filesystem in Userspace
  Copyright (C) 2001-2007  Miklos Szeredi <miklos@szeredi.hu>

  This program can be distributed under the terms of the GNU GPL.
  See the file COPYING.

  gcc -Wall `pkg-config fuse --cflags --libs` fusexmp.c -o fusexmp
*/

#define FUSE_USE_VERSION 26

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#ifdef linux
/* For pread()/pwrite() */
#define _XOPEN_SOURCE 500
#endif

#include <fuse.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
#include <errno.h>
#include <sys/time.h>
#include <string.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#include "config.h"
#include "bootsecto.h"
#include "directoryEntry.h"
#include "lista.h"
#include "listaEntries.h"
#include "conexiones.h"
#include "utils.h"

#define FILEPATH "/home/utn_so/Desarrollo/Workspace/fat32.disk"

#ifdef HAVE_SETXATTR
#include <sys/xattr.h>
#endif

char 				disk = 0;
struct 				TDD_entry *TDD;
int 				tamanioTDD = 0, tamanioCache = -1, maxconexiones, lecturaEnProceso = 0;
bootSector 			bootSec;
char 				fat_table_array[524288];
pthread_mutex_t 	semConexiones, mutexTDD;
sem_t 				semSockContador;
struct t_conexion 	*conexiones;


static int xmp_getattr(const char *path, struct stat *stbuf)
{
	int res = 0;
	unsigned int bool,offset=0;
	directoryEntry entry;

	memset(stbuf, 0, sizeof(struct stat));

	disk = open(FILEPATH, O_RDONLY);

	if (strcmp(path,"/")==0){
		stbuf->st_mode = S_IFDIR | 0755;
		stbuf->st_nlink = 2;
		stbuf->st_size = 0;
	}
	else {
		bool=read_entry(disk,path,2,&entry,&offset);

		if (bool==1){ //si enc la entrada
			if(entry.attribute == 16) {
				stbuf->st_mode = S_IFDIR | 0755;
				stbuf->st_nlink = 2;
				stbuf->st_size = 0;
			}
			if(entry.attribute == 32) {
				stbuf->st_mode = S_IFREG | 0444;
				stbuf->st_nlink = 1;
				stbuf->st_size = entry.fileSize;
			}
		}
		else{
			res = -ENOENT;
		}
	}

     return res;
}

static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
		       off_t offset, struct fuse_file_info *fi)
{
	int bool; unsigned int off=0;
    NodoEntry *lst = NULL,*aux;

    disk = open(FILEPATH, O_RDONLY);
    bool = read_directory(disk,&lst,path,2,&off);//boot.rootCluster=2

     		if(lst!=NULL){

     			while(lst != NULL){
     				aux = lst;
     			    filler(buf,lst->info.name, NULL, 0);
     			    lst= aux->sig;
     			    free(aux);
     			}
     		}

     filler(buf, ".", NULL, 0);
     filler(buf, "..", NULL, 0);

return 0;
}


static int xmp_mknod(const char *path, mode_t mode, dev_t rdev)
{
	int i,enc,nroEntriesAux=0;
	unsigned int cont=1,nroEntries,offset=0,newfirstCluster=0;
	char**newpath,*auxpath,*nameToCreate,*lastPath,**new_extension,extension[3];
	newpath=NULL;
	directoryEntry shortEntry,entry;
	longEntry *longEntries;
	uNodo *aux=NULL,*lst2=NULL;

	disk = open(FILEPATH, O_RDONLY);

	newpath = string_split2(path,'/');

	while (newpath[cont]!=NULL){
		cont++;
	}

	nameToCreate=newpath[cont-1];
	new_extension=string_split2(nameToCreate,'.');
	strcpy(extension,new_extension[2]);

	lastPath=(char*)malloc(strlen(nameToCreate)+1);
	strcpy(lastPath,"/");
	strcat(lastPath,nameToCreate);

	auxpath=(char*)malloc(strlen(path));

	for(i=1;i<(cont-1);i++){
		strcpy(auxpath,"/");
		strcat(auxpath,newpath[i]);
	}

	if (cont==2){				// si el directorio donde se crea es "/"
		entry.firstCluster=2; //boot.rootCluster donde va 2
		enc=1;
		offset=0;
		strcpy(auxpath,"/");
	}
	else{
		enc = read_entry(disk,auxpath,2,&entry,&offset);
	}

	newpath[cont-1]=NULL;

	if(enc==1){

		//creo la entrada nueva (obtengo las n entradas consecutivas necesarias)
		nroEntries = createNameEntry(nameToCreate,&longEntries,&shortEntry);//longEntries-->longentry[0],longentry[1]...
		unsigned int freeEntries[nroEntries];

		//last=strcat(auxpath,*last);
		enc=read_free_entry(disk,&lst2,lastPath,2,&offset); //2 es rootclus

		if (enc==1){
			return -errno;
		}
		else{
			nroEntriesAux=0;
			aux = lst2;

			while(aux != NULL && nroEntriesAux!=nroEntries){

				if(aux->info==aux->sig->info-32){
					freeEntries[nroEntriesAux]=aux->info;
				}
				else{
					nroEntriesAux=-1;
				}

				aux= aux->sig;
				nroEntriesAux++;

			}
			if(lst2==NULL || nroEntriesAux!=nroEntries){
			//pedir un cluster nuevo para auxpath! y enlazarlo en fat
			//si no lo encontro, pedir un cluster libre y devolverlo y ponerlo en ocupado en la FAT en memoria
			//getFreeCluster(newfirstCluster);//pido un cantidad n de cluster libres, persistiendo la fat y que te devuelva el nro de cluster dado
			}
			while(lst2!=NULL){
				unqueue_u(&lst2);
			}

			createShortEntry(&shortEntry,newfirstCluster,32,extension);
			memset(shortEntry.extension,0,3);

			}
		}
		else{
			return -errno;
		}
	return 0;
}

static int xmp_mkdir(const char *path, mode_t mode)
{

		int i,enc,nroEntriesAux=0;
		unsigned int cont=1,nroEntries,offset=0,newfirstCluster=0;
		char**newpath,*auxpath,*nameToCreate,*lastPath,new_extension[3],extension[3];
		newpath=NULL;
		directoryEntry shortEntry,firstEntry[2],entry;
		longEntry *longEntries;
		uNodo *aux=NULL,*lst2=NULL;

		disk = open(FILEPATH, O_RDONLY);

		memset(new_extension,0,3);

		newpath = string_split2(path,'/');

		while (newpath[cont]!=NULL){
			cont++;
		}

		nameToCreate=newpath[cont-1];

		lastPath=(char*)malloc(strlen(nameToCreate)+1);
		strcpy(lastPath,"/");
		strcat(lastPath,nameToCreate);

		auxpath=(char*)malloc(strlen(path));

		for(i=1;i<(cont-1);i++){
			strcpy(auxpath,"/");
			strcat(auxpath,newpath[i]);
		}

		if (cont==2){				// si el directorio donde se crea es "/"
			entry.firstCluster=2; //boot.rootCluster
			enc=1;
			offset=0;
			strcpy(auxpath,"/");
		}
		else{
			enc = read_entry(disk,auxpath,2,&entry,&offset);
		}

		newpath[cont-1]=NULL;

		if(enc==1){

			//creo la entrada nueva (obtengo las n entradas consecutivas necesarias)
			nroEntries = createNameEntry(nameToCreate,&longEntries,&shortEntry);//longEntries-->longentry[0],longentry[1]...

			unsigned int freeEntries[nroEntries];

			//last=strcat(auxpath,*last);
			enc=read_free_entry(disk,&lst2,lastPath,2,&offset);

			if (enc==1){
				return -errno;
			}
			else{
				nroEntriesAux=0;
				aux = lst2;

				while(aux != NULL && nroEntriesAux!=nroEntries){

					if(aux->info==aux->sig->info-32){
						freeEntries[nroEntriesAux]=aux->info;
					}
					else{
						nroEntriesAux=-1;
					}

					aux= aux->sig;
					nroEntriesAux++;
				}
				if(lst2==NULL || nroEntriesAux!=nroEntries){
					//pedir un cluster nuevo para auxpath! y enlazarlo en fat
					//si no lo encontro, pedir un cluster libre y devolverlo y ponerlo en ocupado en la FAT en memoria
					//getFreeCluster(newfirstCluster);//pido un cantidad n de cluster libres, persistiendo la fat y que te devuelva el nro de cluster dado
				}
				while(lst2!=NULL){
					unqueue_u(lst2);
				}
			firstEntry[0].name = malloc(8*sizeof(char));
			memset(firstEntry[0].name,32,8);
			memset(extension,32,3);
			strcpy(firstEntry[0].name,".");
			createShortEntry(&firstEntry[0],entry.firstCluster,16,extension);

			firstEntry[1].name = malloc(8*sizeof(char));
			memset(firstEntry[1].name,32,8);
			memset(extension,32,3);
			strcpy(firstEntry[1].name,"..");
			createShortEntry(&firstEntry[1],newfirstCluster,16,extension);

			createShortEntry(&shortEntry,newfirstCluster,16,new_extension);
			memset(shortEntry.extension,0,3);

			//(necesito entry.firstcluster de la entry devuelta por read_entry para ..)
			//para . entry.firstcluster es el cluster libre que pedi

			//escribir en disco las n entradas de directorio desde LongEntries[nroEntries] hasta LongEntries[0] y shortEntry (en ese orden) en el offset del vector freeEntries
			//y las entradas . y .. (en ese orden) en el contenido de newpath[ultimaEntrada] esta dado por offset de read_directory_2
			}
		}
		else{
			return -errno;
		}
	return 0;
}
static int xmp_unlink(const char *path)
{
//es igual a rmdir
	return 0;
}

static int xmp_rmdir(const char *path)
{
	int res;
	NodoEntry entry;
	unsigned int offset=0;

	disk = open(FILEPATH, O_RDONLY);
	res = read_entry(disk,path,2,&entry,&offset);
	puts("cambio el 1er offset de la entrada a 0xE5");

	return 0;
}


static int xmp_rename(const char *oldPath, const char *pathToCreate)
{
	int i,enc,nroEntriesAux=0;
	unsigned int cont=1,cont_new=1,nroEntries,nroOldEntries,offset=0,newfirstCluster,length=0;
	char**newpath,**createpath,*auxpath,*nameToCreate,*nameOld,*lastPath,new_extension[3],extension[3];
	newpath=NULL;
	directoryEntry shortEntry,entry;
	longEntry *longEntries;
	uNodo *aux=NULL,*lst2=NULL;

	disk = open(FILEPATH, O_RDONLY);

	memset(new_extension,0,3);

	newpath = string_split2(oldPath,'/');

	createpath = string_split2(pathToCreate,'/');

	while (createpath[cont_new]!=NULL){
		cont_new++;
	}

	while (newpath[cont]!=NULL){
		cont++;
	}

	nameOld=newpath[cont-1];
	nameToCreate=createpath[cont_new-1];

	lastPath=(char*)malloc(strlen(nameOld)+1);
	strcpy(lastPath,"/");
	strcat(lastPath,nameOld);

	auxpath=(char*)malloc(strlen(oldPath));

	for(i=1;i<(cont-1);i++){
		strcpy(auxpath,"/");
		strcat(auxpath,newpath[i]);
	}

	if (cont==2){				// si el directorio donde se crea es "/"
		entry.firstCluster=2; //boot.rootCluster
		enc=1;
		offset=0;
		strcpy(auxpath,"/");
	}
	else{
		enc = read_entry(disk,auxpath,2,&entry,&offset);
	}

	newpath[cont-1]=NULL;

	if(enc==1){

		//creo la entrada nueva (obtengo las n entradas consecutivas necesarias)
		nroEntries = createNameEntry(nameToCreate,&longEntries,&shortEntry);//longEntries-->longentry[0],longentry[1]...

		unsigned int freeEntries[nroEntries];

		enc=read_free_entry(disk,&lst2,lastPath,2,&offset);

		if (enc!=1){
			return -errno;
		}
		else{
			nroEntriesAux=0;
			aux = lst2;

			while(aux != NULL && nroEntriesAux!=nroEntries){

				if(aux->info==aux->sig->info-32){
					freeEntries[nroEntriesAux]=aux->info;
				}
				else{
					nroEntriesAux=-1;
				}

				aux= aux->sig;
				nroEntriesAux++;
			}
			if(lst2==NULL || nroEntriesAux!=nroEntries){
				//pedir un cluster nuevo para auxpath! y enlazarlo en fat
				//si no lo encontro, pedir un cluster libre y devolverlo y ponerlo en ocupado en la FAT en memoria
				//getFreeCluster(newfirstCluster);//pido un cantidad n de cluster libres, persistiendo la fat y que te devuelva el nro de cluster dado
			}
			while(lst2!=NULL){
				unqueue_u(lst2);
			}
			length = strlen(nameOld);
			nroEntries = (length/13)+1;
		}

		//escribir en disco las n entradas de directorio desde LongEntries[nroEntries] hasta LongEntries[0] y shortEntry (en ese orden) en el offset del vector freeEntries
		//escribir con 0 en disco las entradas del path viejo dado por offset, offset-32 n veces siendo n el nro de long entries
	}
	else{
		return -errno;
	}

return 0;
}


static int xmp_flush(const char *path, struct fuse_file_info *fi)
{
	char**newpath;
	unsigned int i = 1;
	int cont = 1;


	newpath = string_split2(path,'/');

	if((strcmp(path, "/") == 0)){
		newpath[1]=NULL;
	}

	while (newpath[cont]!=NULL){
		cont++;
	}

	while((strcmp(TDD[i].path, newpath[cont - 1]) != 0)){
		i++;
	}

	if(TDD[i].path == NULL){
		return 0;
	}

	if(TDD[i].cache->modificado == MODIFICADO){
			//bajarCache(path);
	}

	return 0;

}

static int xmp_truncate(const char *path, off_t size)
{
	int res;

	res = truncate(path, size);
	if (res == -1)
		return -errno;

	return 0;
}


static int xmp_open(const char *path, struct fuse_file_info *fi)
{
	int res = -1;
	unsigned int offset=0;
	directoryEntry entry;


	disk = open(FILEPATH, O_RDONLY);

	res = read_entry(disk,path,2,&entry,&offset);

	if(res == 1)
	{
		puts("encontree");
		if(entry.attribute == 32){
			puts("soy un archivooo");
			return 0;
		}
		else {
			puts("soy un directoriooo");
			return -ENOENT;
		}

	}
	else
	{
		puts("no estoyy");
		return -ENOENT;
	}

    return 0;

}

static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
		    struct fuse_file_info *fi)
{
	int 				i, tamanio = 0;
	int 				bytesEnCluster, bytesEnCluster2, clustersAPedir, secPerBloque;
	unsigned int 		primerCluster, bloque, w, w2, primerSector, primerSector2, tamanioCluster, sector;
	sec_arch 			*sectores = NULL;
	div_t 				division;
	struct TDD_entry 	*nodoDelPath, *nodoAnterior;

	tamanioCluster = bootSec.sectorPerCluster*512;

	/*Calculo el tamaño de mi bloque*/
	bloque = (tamanioCluster / 2);
	secPerBloque = bootSec.sectorPerCluster/2;

	/* busco el cluster desde donde quiero leer */
	division = div(offset, tamanioCluster);

	/* si el offset cae en la mitad de un cluster, le sumo uno a la parte entera
	 * si el offset cae justo al final del cluster, le sumo uno a la parte entera para pedir el siguiente*/
	primerCluster = division.quot+1;

	/* posicion dentro del ultimo cluster desde donde quiero leer */
	w =  offset - (division.quot * tamanioCluster);
	bytesEnCluster = tamanioCluster- w;

	division = div((size - bytesEnCluster), tamanioCluster);
	if(division.rem != 0)
	{
		clustersAPedir = division.quot +1;
	}
	else
	{
		clustersAPedir = division.quot;
	}

	if(tamanioCache != 0)
	{
		nodoDelPath = buscarEnTDD(path, nodoAnterior);

		if(nodoDelPath == NULL)
		{
			agregarEntradaTDD(path);

			/* como agrego siempre adelante los nuevos nodos, entonces TDD apunta al nodo de path */
			nodoDelPath = TDD;
		}
	}

	for(i = primerCluster; i <= (clustersAPedir + primerCluster); i++)
	{
		if((i == primerCluster) || (i == (clustersAPedir + primerCluster)))
		{
			if(i == primerCluster)
			{
				if(bloque > w)
				{
					primerSector = getSectorClusterN(primerCluster,bootSec);

					getBloque(nodoDelPath, primerSector, 1, secPerBloque, &sectores, tamanio);

					if ((bloque - w) < size)
					{
						getBloque(nodoDelPath, primerSector, 2, secPerBloque, &sectores, tamanio);
					}
				}
				else
				{
					getBloque(nodoDelPath, primerSector, 2, secPerBloque, &sectores, tamanio);
				}
			}

			if(i == (clustersAPedir + primerCluster))
			{
				bytesEnCluster2 = size - bytesEnCluster - (clustersAPedir * tamanioCluster);

				if(bytesEnCluster2 < 0)
				{
					primerSector2 = getSectorClusterN(clustersAPedir + primerCluster,bootSec);
					w2 = tamanioCluster + bytesEnCluster2;
					if(bloque >= w2 )
					{
						getBloque(nodoDelPath, primerSector2, 1, secPerBloque, &sectores, tamanio);
					}
					else
					{
						getBloque(nodoDelPath, primerSector2, 1, secPerBloque, &sectores, tamanio);
						getBloque(nodoDelPath, primerSector2, 2, secPerBloque, &sectores, tamanio);
					}
				}
			}
		}else{

		//	sector = getSectorClusterN(clusterDeUnArchivo(path, i));
			getBloque(nodoDelPath, sector, 1, secPerBloque, &sectores, tamanio);
			getBloque(nodoDelPath, sector, 2, secPerBloque, &sectores, tamanio);
		}
	}

	armarBuf(&sectores, tamanio, buf, w, w2, bloque, secPerBloque);

	free(sectores);

	return 0;
}

static int xmp_write(const char *path, const char *buf, size_t size,
		     off_t offset, struct fuse_file_info *fi)
{
	int 				bytesEscritos;
	struct TDD_entry	*nodoDelPath, *nodoAnterior;

	if(tamanioCache != 0)
	{
		nodoDelPath = buscarEnTDD(path, nodoAnterior);

		if(nodoDelPath == NULL)
		{
			agregarEntradaTDD(path);

			/* como agrego siempre adelante los nuevos nodos, entonces TDD apunta al nodo de path */
			nodoDelPath = TDD;
		}

		bytesEscritos = writeFile(&nodoDelPath, buf, size, offset, path);
	}
	else
	{
		bytesEscritos = writeFile(NULL, buf, size, offset, path);
	}

	return bytesEscritos;
}


static int xmp_release(const char *path, struct fuse_file_info *fi)
{
	/* Just a stub.	 This method is optional and can safely be left
	   unimplemented */
	//que carajo queres ???!
	(void) path;
	(void) fi;
	return 0;
}


#ifdef HAVE_SETXATTR
/* xattr operations are optional and can safely be left unimplemented */
static int xmp_setxattr(const char *path, const char *name, const char *value,
			size_t size, int flags)
{
	int res = lsetxattr(path, name, value, size, flags);
	if (res == -1)
		return -errno;
	return 0;
}

static int xmp_getxattr(const char *path, const char *name, char *value,
			size_t size)
{
	int res = lgetxattr(path, name, value, size);
	if (res == -1)
		return -errno;
	return res;
}

static int xmp_listxattr(const char *path, char *list, size_t size)
{
	int res = llistxattr(path, list, size);
	if (res == -1)
		return -errno;
	return res;
}

static int xmp_removexattr(const char *path, const char *name)
{
	int res = lremovexattr(path, name);
	if (res == -1)
		return -errno;
	return 0;
}
#endif /* HAVE_SETXATTR */

static struct fuse_operations xmp_oper = {
	.getattr	= xmp_getattr,
	.readdir	= xmp_readdir,
	.mknod		= xmp_mknod,
	.mkdir		= xmp_mkdir,
	.unlink		= xmp_unlink,
	.rmdir		= xmp_rmdir,
	.rename		= xmp_rename,
	.truncate	= xmp_truncate,
	.open		= xmp_open,
	.read		= xmp_read,
	.write		= xmp_write,
	.release	= xmp_release,
	.flush      = xmp_flush,

#ifdef HAVE_SETXATTR
	.setxattr	= xmp_setxattr,
	.getxattr	= xmp_getxattr,
	.listxattr	= xmp_listxattr,
	.removexattr	= xmp_removexattr,
#endif
};

int main(int argc, char *argv[])
{/*
	int retval, retval1, maxconexiones;

	cargarConfig("PFS.config");

	getConfig("tamanioCache", &tamanioCache);
	getConfig("maxconexiones", &maxconexiones);

	retval = sem_init(&semSockContador, 0 ,maxconexiones);

	if(retval == -1) {
		retval1 = errno;
		printf("error en el sem_init: %d >Descripcion: %s\n.", retval1, strerror(errno));
	}

	atexit(clearConfig);
	atexit(clearTDD);

	connect_Raid_PPD();

	if(tamanioCache != 0)
	{
		TDD = NULL;
		pthread_mutex_init(&mutexTDD, NULL);
	}

 	getBootSectorSocket();

	printf("reservedSectors: %d\n.", bootSec.reservedSectors);
	printf("Cantidad de Tablas FAT: %d\n.", bootSec.fatCant);
	printf("Tamanio de la FAT: %d\n.", bootSec.fatSize);
	printf("Cluster donde empieza el Root Directory: %d\n.", bootSec.rootCluster);
	printf("Sectores por cluster: %d\n.", bootSec.sectorPerCluster);

	getFatTable(&fat_table_array);

	// pthread_create();
	return 0;
*/
	//umask(0);
	return fuse_main(argc, argv, &xmp_oper, NULL);

	puts("compilacion excitante");
}
